/*
 * Copyright (c) 2010-2011 Josef Hardi <josef.hardi@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package id.duapuluhdua.dakidaki;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;

import id.duapuluhdua.dakidaki.common.CommonActivity;
import id.duapuluhdua.dakidaki.map.TileMap;

/**
 *
 */
public class TileMapManager
{
  /** Constants */
  private static final int MAX_CACHE_SIZE = 64;

  /** Fields */
  private CommonActivity mParent;
  private Map<String, Bitmap> mMapCacheOfTiles;
  private LinkedList<String> mVecTileQueue;

  /**
   * Creates the tile map manager.
   *
   * @param parent
   *            The main activity.
   */
  public TileMapManager(CommonActivity parent)
  {
    mParent = parent;
    mMapCacheOfTiles = new LinkedHashMap<String, Bitmap>();
    mVecTileQueue = new LinkedList<String>();
  }

  /**
   * Constructs the location path of the tile image.
   *
   * e.g.,
   * <pre>
   * /DakiDaki/GoogleTerrain/[zoom]/[x]/[y].png
   * </pre>
   *
   * @param x
   *            The tile index-x
   * @param y
   *            The tile index-y
   * @param zoom
   *            The zoom level
   * @return The directory path.
   */
  public String getTileImagePath(int x, int y, int zoom)
  {
    StringBuilder dir = new StringBuilder();

    final String root = Environment.getExternalStorageDirectory().getName();

    dir.append(root);
    dir.append("/");
    dir.append(mParent.getApplicationName());
    dir.append("/");
    dir.append(TileMap.TILE_NAME);
    dir.append("/");
    dir.append(zoom);
    dir.append("/");
    dir.append(x);
    dir.append("/");
    dir.append(y);
    dir.append(TileMap.TILE_FORMAT);

    return dir.toString();
  }

  /**
   * Retrieves the tile bitmap from a cache.
   *
   * @param x
   *            The tile index-x.
   * @param y
   *            The tile index-y.
   * @param zoom
   *            The zoom level.
   * @param bUpdateTile
   *            Indicates whether the tile will be updated.
   * @return The tile bitmap object.
   */
  public synchronized Bitmap getTileImage(int x, int y, int zoom, boolean bUpdateTile)
  {
    final String path = getTileImagePath(x, y, zoom);

    return getTileImage(path, bUpdateTile);
  }

  /**
   * Retrieves the tile bitmap from a cache. If the bitmap isn't in the
   * cache, the method reads the image file from the path and stores the
   * bitmap to a cache for future retrieval.
   *
   * @param path
   *            The directory file pathname.
   * @param bUpdateTile
   *            Indicates whether the tile will be updated.
   * @return The tile bitmap object.
   */
  public synchronized Bitmap getTileImage(String path, boolean bUpdateTile)
  {
    if (bUpdateTile) {
      mMapCacheOfTiles.remove(path);
    }

    Bitmap tile = mMapCacheOfTiles.get(path);
    if (tile == null) {
      tile = loadTileImage(path);
    }
    mVecTileQueue.remove(path);
    mVecTileQueue.addFirst(path); // put on the top.

    return tile;
  }


  /**
   * Reads the image file from the given path.
   *
   * @param path
   *            The directory path.
   * @return The tile bitmap object.
   */
  private Bitmap loadTileImage(String path)
  {
    if (mMapCacheOfTiles.size() > MAX_CACHE_SIZE) {
      reduceCache();
    }

    Bitmap tile = null;
    try {
      tile = BitmapFactory.decodeFile(path);
    }
    catch (OutOfMemoryError e) {
      reduceCache();
    }

    if (tile != null) {
      mMapCacheOfTiles.put(path, tile);
    }
    else {
      // TODO: create msg box
    }

    return tile;
  }

  /**
   * Reduce the size of the cache by removing the first half tile images
   * (we consider these are less often used images).
   */
  private void reduceCache()
  {
    for (int i = 0; i < MAX_CACHE_SIZE/2; i++) {
      mMapCacheOfTiles.remove(mVecTileQueue.removeLast());
    }
  }
}
